const CACHE_VERSION = new URL(location).searchParams.get("version");
const BASE_CACHE_FILES = ["/css/home.min.css", "/css/page.min.css", "/css/about.min.css", "/js/theme.min.js", "/site.webmanifest", "/offline/index.html", "/404.html"];
const OFFLINE_CACHE_FILES = ["/css/home.min.css", "/js/theme.min.js", "/offline/index.html"];
const NOT_FOUND_CACHE_FILES = ["/css/home.min.css", "/css/page.min.css", "/js/theme.min.js", "/404.html"];
const OFFLINE_PAGE = "/offline/index.html";
const NOT_FOUND_PAGE = "/404.html";

const CACHE_VERSIONS = {
    assets: "assets-v" + CACHE_VERSION,
    content: "content-v" + CACHE_VERSION,
    offline: "offline-v" + CACHE_VERSION,
    notFound: "404-v" + CACHE_VERSION,
};

const MAX_TTL = {
    "/": 3600,
    html: 3600,
    json: 86400,
    js: 86400,
    css: 86400,
};

const CACHE_BLACKLIST = [
    (str) => {
        return !str.startsWith("http://localhost");
    },
];

const SUPPORTED_METHODS = ["GET"];

function isBlacklisted(url) {
    return CACHE_BLACKLIST.length > 0
        ? !CACHE_BLACKLIST.filter((rule) => {
              if (typeof rule === "function") {
                  return !rule(url);
              } else {
                  return false;
              }
          }).length
        : false;
}

function getFileExtension(url) {
    let extension = url.split(".").reverse()[0].split("?")[0];
    return extension.endsWith("/") ? "/" : extension;
}

function getTTL(url) {
    if (typeof url === "string") {
        let extension = getFileExtension(url);
        if (typeof MAX_TTL[extension] === "number") {
            return MAX_TTL[extension];
        } else {
            return null;
        }
    } else {
        return null;
    }
}

function installServiceWorker() {
    return Promise.all([
        caches.open(CACHE_VERSIONS.assets).then((cache) => {
            return cache.addAll(BASE_CACHE_FILES);
        }),
        caches.open(CACHE_VERSIONS.offline).then((cache) => {
            return cache.addAll(OFFLINE_CACHE_FILES);
        }),
        caches.open(CACHE_VERSIONS.notFound).then((cache) => {
            return cache.addAll(NOT_FOUND_CACHE_FILES);
        }),
    ]).then(() => {
        return self.skipWaiting();
    });
}

function cleanupLegacyCache() {
    let currentCaches = Object.keys(CACHE_VERSIONS).map((key) => {
        return CACHE_VERSIONS[key];
    });

    return new Promise((resolve, reject) => {
        caches
            .keys()
            .then((keys) => {
                return (legacyKeys = keys.filter((key) => {
                    return !~currentCaches.indexOf(key);
                }));
            })
            .then((legacy) => {
                if (legacy.length) {
                    Promise.all(
                        legacy.map((legacyKey) => {
                            return caches.delete(legacyKey);
                        })
                    )
                        .then(() => {
                            resolve();
                        })
                        .catch((err) => {
                            reject(err);
                        });
                } else {
                    resolve();
                }
            })
            .catch(() => {
                reject();
            });
    });
}

function precacheUrl(url) {
    if (!isBlacklisted(url)) {
        caches.open(CACHE_VERSIONS.content).then((cache) => {
            cache
                .match(url)
                .then((response) => {
                    if (!response) {
                        return fetch(url);
                    } else {
                        return null;
                    }
                })
                .then((response) => {
                    if (response) {
                        return cache.put(url, response.clone());
                    } else {
                        return null;
                    }
                });
        });
    }
}

self.addEventListener("install", (event) => {
    event.waitUntil(Promise.all([installServiceWorker(), self.skipWaiting()]));
});

self.addEventListener("activate", (event) => {
    event.waitUntil(
        Promise.all([cleanupLegacyCache(), self.clients.claim(), self.skipWaiting()]).catch((err) => {
            event.skipWaiting();
        })
    );
});

self.addEventListener("fetch", (event) => {
    event.respondWith(
        caches.open(CACHE_VERSIONS.content).then((cache) => {
            return cache
                .match(event.request)
                .then((response) => {
                    if (response) {
                        let headers = response.headers.entries();
                        let date = null;

                        for (let pair of headers) {
                            if (pair[0] === "date") {
                                date = new Date(pair[1]);
                            }
                        }

                        if (date) {
                            let age = parseInt((new Date().getTime() - date.getTime()) / 1000);
                            let ttl = getTTL(event.request.url);

                            if (ttl && age > ttl) {
                                return new Promise((resolve) => {
                                    return fetch(event.request.clone())
                                        .then((updatedResponse) => {
                                            if (updatedResponse) {
                                                cache.put(event.request, updatedResponse.clone());
                                                resolve(updatedResponse);
                                            } else {
                                                resolve(response);
                                            }
                                        })
                                        .catch(() => {
                                            resolve(response);
                                        });
                                }).catch((err) => {
                                    return response;
                                });
                            } else {
                                return response;
                            }
                        } else {
                            return response;
                        }
                    } else {
                        return null;
                    }
                })
                .then((response) => {
                    if (response) {
                        return response;
                    } else {
                        return fetch(event.request.clone())
                            .then((response) => {
                                if (response.status < 400) {
                                    if (~SUPPORTED_METHODS.indexOf(event.request.method) && !isBlacklisted(event.request.url)) {
                                        cache.put(event.request, response.clone());
                                    }
                                    return response;
                                } else {
                                    return caches.open(CACHE_VERSIONS.notFound).then((cache) => {
                                        return cache.match(NOT_FOUND_PAGE);
                                    });
                                }
                            })
                            .then((response) => {
                                if (response) {
                                    return response;
                                }
                            })
                            .catch(() => {
                                return caches.open(CACHE_VERSIONS.offline).then((offlineCache) => {
                                    return offlineCache.match(OFFLINE_PAGE);
                                });
                            });
                    }
                })
                .catch((error) => {
                    console.error("  Error in fetch handler:", error);
                    throw error;
                });
        })
    );
});

self.addEventListener("message", (event) => {
    if (typeof event.data === "object" && typeof event.data.action === "string") {
        switch (event.data.action) {
            case "cache":
                precacheUrl(event.data.url);
                break;
            default:
                console.log("Unknown action: " + event.data.action);
                break;
        }
    }
});

